#include "glesrender.h"

#ifdef __APPLE__
#include <OpenGLES/ES3/gl.h>
#else
#include <GLES3/gl3.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#endif

#include "glesProgram.h"
#include <stdio.h>
#include <windows.h>

struct gles_window_t
{
    EGLNativeWindowType         m_native_window;
    gles_window_t()
    {
        m_native_window = NULL;
    }
};

struct gles_render_t
{
    gles_window_t*          m_gles_window;
    EGLNativeDisplayType    m_native_display;
    EGLDisplay              m_egl_display;
    EGLContext              m_egl_context;
    EGLSurface              m_egl_surface;
    GLuint                  m_gles_program;
    GLuint                  m_gles_sample_location;
    gles_render_t(gles_window_t* window)
    {
        m_gles_window = window;
        m_native_display = EGL_DEFAULT_DISPLAY;
        m_egl_display = EGL_NO_DISPLAY;
        m_egl_surface = EGL_NO_SURFACE;
        m_egl_context = EGL_NO_CONTEXT;
        m_gles_program = 0;
        m_gles_sample_location = -1;
    }
};

struct gles_texture_t
{
    gles_render_t*  m_gles_render;
    int             m_width, m_height;
    GLint           m_gl_pixel_format;
    GLuint          m_gl_texture_id;
    gles_texture_t(gles_render_t* render)
    {
        m_gles_render = render;
        m_width = m_height = 0;
        m_gl_pixel_format = GL_RGB;
        m_gl_texture_id = 0;//0 for opengl reserved texture ID
    }
};

gles_window_t* gles_window_create(native_window_t native_window)
{
    gles_window_t* gles_window = new gles_window_t;
    gles_window->m_native_window = (EGLNativeWindowType)native_window;
    return gles_window;
}
int gles_window_destroy(gles_window_t* esWindow)
{
    if (esWindow)
    {
        delete esWindow;
    }
    return 0;
}

static EGLint GetContextRenderableType(EGLDisplay eglDisplay)
{
#ifdef EGL_KHR_create_context
    const char *extensions = eglQueryString(eglDisplay, EGL_EXTENSIONS);

    // check whether EGL_KHR_create_context is in the extension string
    if (extensions != NULL && strstr(extensions, "EGL_KHR_create_context"))
    {
        // extension is supported
        return EGL_OPENGL_ES3_BIT_KHR;
    }
#endif
    // extension is not supported
    return EGL_OPENGL_ES2_BIT;
}

static char vShaderStr[] =
"#version 300 es                            \n"
"layout(location = 0) in vec4 a_position;   \n"
"layout(location = 1) in vec2 a_texCoord;   \n"
"out vec2 v_texCoord;                       \n"
"void main()                                \n"
"{                                          \n"
"   gl_Position = a_position;               \n"
"   v_texCoord = a_texCoord;                \n"
"}                                          \n";

static char fShaderStr[] =
"#version 300 es                                     \n"
"precision mediump float;                            \n"
"in vec2 v_texCoord;                                 \n"
"layout(location = 0) out vec4 outColor;             \n"
"uniform sampler2D s_texture;                        \n"
"void main()                                         \n"
"{                                                   \n"
"  outColor = texture( s_texture, v_texCoord );      \n"
"}                                                   \n";



gles_render_t* gles_render_create(gles_window_t* esWindow)
{
    if (!esWindow)
        return NULL;
    gles_render_t* esRender = new gles_render_t(esWindow);
    EGLConfig config;
    EGLint majorVersion;
    EGLint minorVersion;
    EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
    std::string errMsg;

    esRender->m_native_display = ::GetDC((HWND)esWindow->m_native_window);
    esRender->m_egl_display = eglGetDisplay(esRender->m_native_display);
    if (esRender->m_egl_display == EGL_NO_DISPLAY)
    {
        goto fail;
    }
    if (!eglInitialize(esRender->m_egl_display, &majorVersion, &minorVersion))
    {
        goto fail;
    }
    {
        EGLint numConfigs = 0;
        EGLint attribList[] =
        {
            EGL_RED_SIZE,       8,
            EGL_GREEN_SIZE,     8,
            EGL_BLUE_SIZE,      8,
            EGL_ALPHA_SIZE,     EGL_DONT_CARE,
            EGL_DEPTH_SIZE,     EGL_DONT_CARE,
            EGL_STENCIL_SIZE,   EGL_DONT_CARE,
            EGL_SAMPLE_BUFFERS, 0,
            // if EGL_KHR_create_context extension is supported, then we will use
            // EGL_OPENGL_ES3_BIT_KHR instead of EGL_OPENGL_ES2_BIT in the attribute list
            EGL_RENDERABLE_TYPE, GetContextRenderableType(esRender->m_egl_display),
            EGL_NONE
        };

        // Choose config
        if (!eglChooseConfig(esRender->m_egl_display, attribList, &config, 1, &numConfigs))
        {
            goto fail;
        }

        if (numConfigs < 1)
        {
            goto fail;
        }
    }
    esRender->m_egl_surface = eglCreateWindowSurface(esRender->m_egl_display, 
        config, esRender->m_gles_window->m_native_window, NULL);
    if (esRender->m_egl_surface == EGL_NO_SURFACE)
    {
        goto fail;
    }
    esRender->m_egl_context = eglCreateContext(esRender->m_egl_display, config, EGL_NO_CONTEXT, contextAttribs);
    if (esRender->m_egl_context == EGL_NO_CONTEXT)
    {
        goto fail;
    }
    if (!eglMakeCurrent(esRender->m_egl_display, esRender->m_egl_surface, esRender->m_egl_surface, esRender->m_egl_context))
    {
        goto fail;
    }
    
    esRender->m_gles_program = gles_load_program(vShaderStr, fShaderStr,errMsg);
    if (esRender->m_gles_program == 0)
    {
        goto fail;
    }
    esRender->m_gles_sample_location = glGetUniformLocation(esRender->m_gles_program, "s_texture");
    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);
    return esRender;
fail:
    if (esRender->m_gles_program)
    {
        glDeleteProgram(esRender->m_gles_program);
        esRender->m_gles_program = 0;
    }
    if (esRender->m_egl_display != EGL_NO_DISPLAY)
    {
        eglMakeCurrent(esRender->m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    }
    if (esRender->m_egl_context != EGL_NO_CONTEXT)
    {
        eglDestroyContext(esRender->m_egl_display,esRender->m_egl_context);
        esRender->m_egl_context = EGL_NO_CONTEXT;
    }
    if (esRender->m_egl_surface != EGL_NO_SURFACE)
    {
        eglDestroySurface(esRender->m_egl_display, esRender->m_egl_surface);
        esRender->m_egl_surface = EGL_NO_SURFACE;
    }
    if (esRender->m_egl_display)
    {
        eglTerminate(esRender->m_egl_display);
        esRender->m_egl_display = EGL_NO_DISPLAY;
    }
    if (esRender->m_native_display)
    {
        ::ReleaseDC(esRender->m_gles_window->m_native_window, esRender->m_native_display);
        esRender->m_native_display = NULL;
    }
    delete esRender;
    return NULL;
}

int gles_render_destroy(gles_render_t* esRender)
{
    if (esRender->m_gles_program)
    {
        glDeleteProgram(esRender->m_gles_program);
        esRender->m_gles_program = 0;
    }
    if (esRender->m_egl_display != EGL_NO_DISPLAY)
    {
        eglMakeCurrent(esRender->m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    }
    if (esRender->m_egl_context != EGL_NO_CONTEXT)
    {
        eglDestroyContext(esRender->m_egl_display, esRender->m_egl_context);
        esRender->m_egl_context = EGL_NO_CONTEXT;
    }
    if (esRender->m_egl_surface != EGL_NO_SURFACE)
    {
        eglDestroySurface(esRender->m_egl_display, esRender->m_egl_surface);
        esRender->m_egl_surface = EGL_NO_SURFACE;
    }
    if (esRender->m_egl_display)
    {
        eglTerminate(esRender->m_egl_display);
        esRender->m_egl_display = EGL_NO_DISPLAY;
    }
    if (esRender->m_native_display)
    {
        ::ReleaseDC(esRender->m_gles_window->m_native_window, esRender->m_native_display);
        esRender->m_native_display = NULL;
    }
    delete esRender;
    return 0;
}
int gles_render_set_clear_color(gles_render_t* esRender, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
    if (!esRender->m_gles_program)
        return -1;
    glClearColor(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
    return 0;
}
int gles_render_clear(gles_render_t* esRender)
{
    if (!esRender->m_gles_program)
        return -1;
    glClear(GL_COLOR_BUFFER_BIT);
}

gles_texture_t* gles_texture_create(gles_render_t* render, GlesPixelFormat format, int width, int height)
{
    gles_texture_t* newTexture = new gles_texture_t(render);

    GLuint textureID = 0;
    glGenTextures(1, &textureID);
    if (textureID == 0)
    {
        goto fail;
    }

    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    newTexture->m_gl_pixel_format = GL_RGB;
    newTexture->m_width = width;
    newTexture->m_height = height;
    newTexture->m_gl_texture_id = textureID;
    return newTexture;
fail:
    if (textureID != 0)
    {
        glDeleteTextures(1, &textureID);
    }
    delete newTexture;
    return NULL;
}

int gles_texture_destroy(gles_texture_t* esTexture)
{
    if (!esTexture)
        return 0;
    if (esTexture->m_gl_texture_id != 0)
    {
        glDeleteTextures(1, &esTexture->m_gl_texture_id);
    }
    delete esTexture;
    return 0;
}

int gles_texture_update(gles_texture_t* dstTexture, const gles_rect_t* dstRect, const uint8_t* pixel, int pitch)
{
    if (!dstTexture || !dstTexture->m_gl_texture_id)
        return -1;
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBindTexture(GL_TEXTURE_2D, dstTexture->m_gl_texture_id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dstTexture->m_width, dstTexture->m_height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixel);

    glBindTexture(GL_TEXTURE_2D, 0);
    return 0;
}

int gles_render_copy(gles_render_t* dstRender, gles_texture_t* srcTexture, const gles_rect_t* dstRect, const gles_rect_t* srcRect)
{
    GLfloat vVertices[] =
    {   -1.0f,  1.0f, 0.0f,  // Position 0
        0.0f,  0.0f,        // TexCoord 0
        -1.0f, -1.0f, 0.0f,  // Position 1
        0.0f,  1.0f,        // TexCoord 1
        1.0f, -1.0f, 0.0f,  // Position 2
        1.0f,  1.0f,        // TexCoord 2
        1.0f,  1.0f, 0.0f,  // Position 3
        1.0f,  0.0f         // TexCoord 3
    };

    GLushort indices[] = { 0, 2, 3, 0, 1, 2 };

    RECT clientRect;
    ::GetClientRect(dstRender->m_gles_window->m_native_window, &clientRect);
    // Set the viewport
    glViewport(0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);

    // Use the program object
    glUseProgram(dstRender->m_gles_program);

    // Load the vertex position
    glVertexAttribPointer(0, 3, GL_FLOAT,
        GL_FALSE, 5 * sizeof(GLfloat), vVertices);
    // Load the texture coordinate
    glVertexAttribPointer(1, 2, GL_FLOAT,
        GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3]);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    // Bind the texture
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, srcTexture->m_gl_texture_id);

    // Set the sampler texture unit to 0
    glUniform1i(dstRender->m_gles_sample_location, 0);

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);

    glBindTexture(GL_TEXTURE_2D, 0);
    
    return 0;
}

int gles_render_present(gles_render_t* esRender)
{
    if (!esRender)
        return -1;
    if (esRender->m_egl_display == EGL_NO_DISPLAY || esRender->m_egl_surface == EGL_NO_SURFACE)
        return -1;
    eglSwapBuffers(esRender->m_egl_display, esRender->m_egl_surface);
    return 0;
}


